﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using jade.core;
using jade.core.behaviours;
using jade.lang.acl;
namespace CMS1 {
    class HandleDeals : CyclicBehaviour {
        private const int RECEIVING_OFFERS_STEP = 2100;
        private const int OFFER_SELECTION_STEP = 2101;
        private const int FINALIZE_DEALS = 2102;
        private const int NEGOTIATION = 2103;
        private const int DONE_DEALING = 2104;

        private const int HOTEL_REDUCTION = 2104;
        private const int CATER_REDUCTION = 2105;

        public const int HOTEL_OFFER = 2106;
        public const int CATER_OFFER = 2107;


        private int step;
        private int receivedResidenceOffers = 0;
        private int receivedCaterOffers = 0;

        //private bool offersAccepted = false;
        //private bool bestHotelRefusedReduction;
        //private bool bestCatererRefusedReduction;

        private List<ServiceProviderTotalCost> hotelsTotalCosts;
        private List<ServiceProviderTotalCost> caterersTotalCosts;
        private List<HotelRoom> offeredRooms;

        private int overBudget;

        MessageTemplate externalMsg;
        DatabaseWrapper db;
        DealingOutcome dealingOutcome;
        //public const string RESIDENCE_BEST_OFFER = "residence_best_offer";

        public HandleDeals(Agent agent)
            : base(agent) {
            step = RECEIVING_OFFERS_STEP;
            externalMsg = MessageTemplate.MatchSender(new AID("GatewayAgent", AID.ISLOCALNAME));
            db = ((Communicator)myAgent).db;
            hotelsTotalCosts = new List<ServiceProviderTotalCost>();
            caterersTotalCosts = new List<ServiceProviderTotalCost>();
            offeredRooms = new List<HotelRoom>();
            dealingOutcome = new DealingOutcome();
        }

        public DealingOutcome getDealingOutcome() {
            return dealingOutcome;
        }

        public override void action() {
            ACLMessage aclMsg;
            switch (step) {
                case RECEIVING_OFFERS_STEP:
                    aclMsg = myAgent.receive(externalMsg);
                    if (aclMsg != null) {
                        Message msg = JsonConvert.DeserializeObject<Message>(aclMsg.getContent());
                        if (msg.Purpose == HOTEL_OFFER) {
                            Offer[] residencesCosts = JsonConvert.DeserializeObject<Offer[]>(msg.Data);
                            computeCosts(HOTEL_OFFER, residencesCosts);
                            receivedResidenceOffers++;
                            if (receivedAllOffers()) {
                                step = OFFER_SELECTION_STEP;
                            } else {
                                block();
                            }
                        } else if (msg.Purpose == CATER_OFFER) {
                            Offer[] caterCosts = JsonConvert.DeserializeObject<Offer[]>(msg.Data);
                            computeCosts(CATER_OFFER, caterCosts);
                            receivedCaterOffers++;
                            if (receivedAllOffers()) {
                                step = OFFER_SELECTION_STEP;
                            } else {
                                block();
                            }
                        } else {
                            block();
                        }
                    } else {
                        if (((Communicator)myAgent).responsesTimeOut) {
                            if (hotelsTotalCosts.Count == 0 || caterersTotalCosts.Count == 0) {
                                // generate report "Timed out with insuffecient responses"
                                Message msg = new Message();
                                msg.Purpose = Communicator.DEALING_ISSUE;
                                List<object> content = new List<object>();
                                content.Add(1); // significance
                                content.Add((((Communicator)myAgent).Timeout / 1000 * 60 * 60 * 24) + " Days have passed yet received " +
                                    hotelsTotalCosts.Count + " Residence offer(s) and " + 
                                    caterersTotalCosts.Count + " Cater offer(s), which is insufficient for the system to perform the dealing.");
                                msg.Data = JsonConvert.SerializeObject(content);
                                ACLMessage aclMessage = new ACLMessage(ACLMessage.INFORM);
                                aclMessage.addReceiver(new AID("Reporter", AID.ISLOCALNAME));
                                aclMessage.setContent(JsonConvert.SerializeObject(msg));
                                myAgent.send(aclMessage);
                                dealingOutcome.Outcome = DealingOutcome.TIMED_OUT;
                                block();
                            } else {
                                step = OFFER_SELECTION_STEP;
                            }
                        } else {
                            block();
                        }
                    }
                    break;
                case OFFER_SELECTION_STEP:
                    chooseOffer();
                    break;
                case FINALIZE_DEALS:
                    if (dealingOutcome.Outcome == DealingOutcome.OFFERS_ACCEPTED) {//offersAccepted) {
                        string[][] rejectedHotelsEmails = (string[][])db.select("email").from("Hotel, Hotel_offer").where("accepted='0' and hotel_id=Hotel.id").execute();
                        if (rejectedHotelsEmails != null && rejectedHotelsEmails.Length > 0) {
                            string emailsGroup = "";
                            foreach (string[] rejectedHotelEmail in rejectedHotelsEmails) {
                                emailsGroup += rejectedHotelEmail + ",";
                            }
                            emailsGroup = emailsGroup.Substring(0, emailsGroup.Length - 1);
                            ((Communicator)myAgent).sendRejectEmailsToHotels(emailsGroup.Split(','));
                        }
                        string[][] rejectedCaterersEmails = (string[][])db.select("email").from("Cater_provider, Cater_provider_offer").where("accepted='0' and cater_provider_id=Cater_provider.id").execute();
                        if (rejectedCaterersEmails != null && rejectedCaterersEmails[0].Length > 0) {
                            string emailsGroup = "";
                            foreach (string[] rejectedCatererEmail in rejectedCaterersEmails) {
                                emailsGroup += rejectedCatererEmail + ",";
                            }
                            ((Communicator)myAgent).sendRejectEmailsToCaterProviders(emailsGroup.Split(','));
                        }
                    } else {
                        string message = "Thanks for your co-operation but we coudn't find a suitable offer.";
                        string[] allHotelsEmails = (string[])db.selectColumn("email").from("Hotel").where(GatewayAgent.myConf).execute();
                        string[] allCaterProvidersEmails = (string[])db.selectColumn("email").from("Cater_provider").where(GatewayAgent.myConf).execute();
                        string[] allEmails = allHotelsEmails.Concat(allCaterProvidersEmails).ToArray();
                        ((Communicator)myAgent).sendInformEmails(allEmails, message);
                    }
                    step = DONE_DEALING;
                    break;
                case NEGOTIATION:
                    aclMsg = myAgent.receive(externalMsg);
                    if (aclMsg != null) {
                        Message msg = JsonConvert.DeserializeObject<Message>(aclMsg.getContent());
                        if (msg.Purpose == HOTEL_REDUCTION) {
                            /*if (offersAccepted) {
                                // accepted in cater
                                //hotelsTotalCosts.Sort();
                                //ServiceProviderTotalCost residenceBestOffer = hotelsTotalCosts.First();
                                ServiceProviderTotalCost residenceBestOffer = hotelsTotalCosts.First();
                                string email = (string)db.selectCell("email").from("Hotel").where("id='" + residenceBestOffer.ServiceProviderID + "'").execute();
                                ((Communicator)myAgent).sendReductionCancellationEmail(email);
                            } else {*/
                            List<object> result = JsonConvert.DeserializeObject<List<object>>(msg.Data);
                            bool reductionRequestAccepted = (bool)result[0];
                            if (reductionRequestAccepted) {
                                //getDataStore().put(OFFERS_ACCEPTED, reductionRequestAccepted);
                                //offersAccepted = true;
                                dealingOutcome.Outcome = DealingOutcome.OFFERS_ACCEPTED;
                                dealingOutcome.OfferAcceptanceMethod = DealingOutcome.AFTER_NEGOTIATION;
                                dealingOutcome.ResidenceTotalCost = hotelsTotalCosts[0].TotalCost;
                                dealingOutcome.CaterTotalCost = caterersTotalCosts[0].TotalCost;
                                dealingOutcome.TotalCost = dealingOutcome.ResidenceTotalCost + dealingOutcome.CaterTotalCost;
                                foreach (HotelRoom room in offeredRooms) {
                                    if (room.HotelID == hotelsTotalCosts[0].ServiceProviderID) {
                                        dealingOutcome.RoomsCosts[room.Type - 1] = room.OfferedCost;
                                    }
                                }
                                dealingOutcome.CatersCosts = (object[][])db.select("cater, type, cost").from("Cater, Cater_provider").
                                    where("cater_service_id=Cater.id").and("cater_provider_id='" + caterersTotalCosts[0].ServiceProviderID + "'").
                                    and(GatewayAgent.myConf).execute();

                                int serviceProviderID = (int)result[1];
                                db.update("Hotel_offer").set("accepted", "1").where("hotel_id='" + serviceProviderID + "'").execute();
                                db.update("Cater_provider_offer").set("accepted", "1").where("cater_provider_id='" + caterersTotalCosts[0].ServiceProviderID + "'").execute();
                                string hotelAcceptanceMessage = "Thanks for understanding our situation, We are glad to inform you that we have accepted your serving offer, thanks for your co-operation.";
                                string hotelEmail = ((string)db.selectCell("email").from("Hotel").where("id='" + hotelsTotalCosts[0].ServiceProviderID + "'").execute());
                                string catererAcceptanceMessage = "We are glad to inform you that we have accepted your serving offer, thanks for your co-operation.";
                                string caterProviderEmail = ((string)db.selectCell("email").from("Cater_provider").where("id='" + serviceProviderID + "'").execute());
                                ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { hotelEmail }, hotelAcceptanceMessage);
                                ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { caterProviderEmail }, catererAcceptanceMessage);
                                /*if (!bestCatererRefusedReduction) {
                                    ServiceProviderTotalCost caterBestOffer = caterersTotalCosts.First();
                                    string email = (string)db.selectCell("email").from("Cater_provider").where("id='" + caterBestOffer.ServiceProviderID + "'").execute();
                                    ((Communicator)myAgent).sendReductionCancellationEmail(email);
                                } else {
                                    ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { caterProviderEmail }, catererAcceptanceMessage);
                                }*/
                                step = FINALIZE_DEALS;
                            } else {
                                decreaseCostByDistributingService();
                                step = FINALIZE_DEALS;
                                /*bestHotelRefusedReduction = true;
                                if (bestCatererRefusedReduction) {
                                    decreaseCostByDistributingService();
                                    step = FINALIZE_DEALS;
                                }*/
                            }
                            //}
                        } /*else if (msg.Purpose == CATER_REDUCTION) {
                            /*if (offersAccepted) {
                                // accepted in cater
                                //caterersTotalCosts.Sort();
                                ServiceProviderTotalCost caterBestOffer = caterersTotalCosts.First();
                                string email = (string)db.selectCell("email").from("Cater_provider").where("id='" + caterBestOffer.ServiceProviderID + "'").execute();
                                ((Communicator)myAgent).sendReductionCancellationEmail(email);
                            } else {*
                            List<object> result = JsonConvert.DeserializeObject<List<object>>(msg.Data);
                            bool reductionRequestAccepted = (bool)result[0];
                            if (reductionRequestAccepted) {
                                //getDataStore().put(OFFERS_ACCEPTED, reductionRequestAccepted);
                                //offersAccepted = true;
                                dealingOutcome.Outcome = DealingOutcome.OFFERS_ACCEPTED;
                                dealingOutcome.OfferAcceptanceMethod = DealingOutcome.AFTER_NEGOTIATION;

                                int serviceProviderID = (int)result[1];
                                db.update("Hotel_offer").set("accepted", "1").where("hotel_id='" + hotelsTotalCosts[0].ServiceProviderID + "'").execute();
                                db.update("Cater_provider_offer").set("accepted", "1").where("cater_provider_id='" + serviceProviderID + "'").execute();
                                string catererAcceptanceMessage = "Thanks for understanding our situation, We are glad to inform you that we have accepted your serving offer, thanks for your co-operation.";
                                string hotelEmail = ((string)db.selectCell("email").from("Hotel").where("id='" + hotelsTotalCosts[0].ServiceProviderID + "'").execute());
                                string hotelAcceptanceMessage = "We are glad to inform you that we have accepted your serving offer, thanks for your co-operation.";
                                string caterProviderEmail = ((string)db.selectCell("email").from("Cater_provider").where("id='" + serviceProviderID + "'").execute());
                                ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { caterProviderEmail }, catererAcceptanceMessage);
                                if (!bestHotelRefusedReduction) {
                                    ServiceProviderTotalCost residenceBestOffer = hotelsTotalCosts.First();
                                    string email = (string)db.selectCell("email").from("Hotel").where("id='" + residenceBestOffer.ServiceProviderID + "'").execute();
                                    ((Communicator)myAgent).sendReductionCancellationEmail(email);
                                } else {
                                    ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { hotelEmail }, hotelAcceptanceMessage);
                                }
                                step = FINALIZE_DEALS;
                                // sendOfferAcceptance
                                // generateReport
                            } else {
                                bestCatererRefusedReduction = true;
                                if (bestHotelRefusedReduction) {
                                    decreaseCostByDistributingService();
                                    step = FINALIZE_DEALS;
                                }
                            }
                            //}
                        } */else {
                            block();
                        }
                    } else {
                        block();
                    }
                    break;
                case DONE_DEALING:
                    block();
                    break;
            }
        }

        public bool receivedAllOffers() {
            if (receivedResidenceOffers == ((Communicator)myAgent).ResidenceMessages &&
                receivedCaterOffers == ((Communicator)myAgent).CaterMessages) {
                return true;
            }
            return false;
        }

        private void computeCosts(int provider, Offer[] offers) {
            int totalResidenceCost = 0;
            int id = offers[0].ServiceProviderID;
            switch (provider) {
                case HOTEL_OFFER:
                    foreach (Offer offer in offers) {
                        int offerCost;
                        int discount = offer.CorporationDiscount + offer.SpecialGuestsDiscount;
                        if (discount > 0) {
                            offerCost = discount * offer.Cost / 100;
                            //serviceProviderTotalCosts += costAfterDiscount;
                        } else {
                            offerCost = offer.Cost;
                        }
                        int nRoom = ((int[][])db.select("count(residence_service_id)").from("Attendee").
                            where("residence_service_id='" + offer.ServiceID + "'").and(GatewayAgent.myConf).execute())[0][0];
                        int[] attendeesIDs = (int[])db.selectColumn("id").from("Attendee").
                            where("residence_service_id='" + offer.ServiceID + "'").and(GatewayAgent.myConf).execute();
                        int totalDays = 0;
                        foreach (int attendeeID in attendeesIDs) {
                            DateTime[][] arrival_departue_dates = (DateTime[][])db.select("arrival_date, departure_date").
                                from("Attendee_arrival_details").where("attendee_id='" + attendeeID + "'").and(GatewayAgent.myConf).execute();
                            totalDays += arrival_departue_dates[0][1].Subtract(arrival_departue_dates[0][0]).Days;
                        }
                        HotelRoom room = new HotelRoom();
                        room.Type = offer.ServiceType;
                        room.HotelID = offer.ServiceProviderID;
                        room.OfferedCost = offerCost;
                        room.TotalCost = nRoom * totalDays * offerCost;
                        offeredRooms.Add(room);
                        totalResidenceCost += room.TotalCost;
                    }
                    hotelsTotalCosts.Add(new ServiceProviderTotalCost(id, totalResidenceCost));
                    break;
                case CATER_OFFER:
                    // offering days are in the page, the provider should set the cost considering the number of days
                    int totalCost = 0;
                    foreach (Offer offer in offers) {
                        totalCost += offer.Cost;
                    }
                    int[] discounts = ((int[][])db.select("corporation_discount, special_guests_discount").from("Cater_provider").
                        where("id='" + id + "'").and(GatewayAgent.myConf).execute())[0];
                    int totalDiscount = discounts[0] + discounts[1];
                    if (totalDiscount > 0) {
                        totalCost = totalDiscount * totalCost / 100;
                    }
                    totalResidenceCost = totalCost;
                    caterersTotalCosts.Add(new ServiceProviderTotalCost(id, totalResidenceCost));
                    break;
            }
        }

        private void chooseOffer() {
            //hotelsTotalCosts.OrderByDescending(i => i.TotalCost);
            hotelsTotalCosts.Sort();
            ServiceProviderTotalCost residenceBestOffer = hotelsTotalCosts[0];//.First();
            //getDataStore().put(RESIDENCE_BEST_OFFER, residenceBestOffer);

            caterersTotalCosts.Sort();
            ServiceProviderTotalCost caterBestOffer = caterersTotalCosts[0];//.First();
            //getDataStore().put(CATER_BEST_OFFER, caterBestOffer);
            //ServiceProviderTotalCost caterBestOffer = (ServiceProviderTotalCost)getDataStore().get(HandleCaterDeals.CATER_BEST_OFFER);

            int budget = (int)db.selectCell("budget").from("Deals_details").execute();
            int totalCost = residenceBestOffer.TotalCost + caterBestOffer.TotalCost;
            overBudget = totalCost - budget;
            if (overBudget > 0) {
                dealingOutcome.OverBudget = overBudget;
                int maxAllowableReduction = (residenceBestOffer.TotalCost * 3) / 100;
                if (overBudget <= maxAllowableReduction) {
                    //string hotelEmail = (string)db.selectCell("email").from("Hotel").where("id='" + residenceBestOffer.ServiceProviderID + "'").execute();
                    string[][] result = (string[][])db.select("email, currency").from("Hotel").where("id='" + residenceBestOffer.ServiceProviderID + "'").execute();
                    ((Communicator)myAgent).sendReductionRequestEmail(result[0][0], totalCost, overBudget, result[0][1]);
                    //string caterProviderEmail = (string)db.selectCell("email").from("Cater_provider").where("id='" + residenceBestOffer.ServiceProviderID + "'").execute();
                    /*int caterMaxAllowableReduction = (caterBestOffer.TotalCost * 3) / 100;
                    if (overBudget <= caterMaxAllowableReduction) {
                        result = (string[][])db.select("email, currency").from("Cater_provider").where("id='" + caterBestOffer.ServiceProviderID + "'").execute();
                        ((Communicator)myAgent).sendReductionRequestEmail(result[0][0], totalCost, overBudget, result[0][1]);
                    }*/
                    step = NEGOTIATION;
                    block();
                    // request cost reduction from hotel
                    // request cost reduction from cater provider
                } else {
                    decreaseCostByDistributingService();
                    step = FINALIZE_DEALS;
                }
            } else {
                //getDataStore().put(OFFERS_ACCEPTED, true);
                //offersAccepted = true;
                dealingOutcome.Outcome = DealingOutcome.OFFERS_ACCEPTED;
                dealingOutcome.OfferAcceptanceMethod = DealingOutcome.NORMAL_ACCEPTANCE;
                dealingOutcome.TotalCost = totalCost;
                dealingOutcome.ResidenceTotalCost = residenceBestOffer.TotalCost;
                dealingOutcome.CaterTotalCost = caterBestOffer.TotalCost;
                foreach (HotelRoom room in offeredRooms) {
                    if (room.HotelID == residenceBestOffer.ServiceProviderID) {
                        dealingOutcome.RoomsCosts[room.Type - 1] = room.OfferedCost;
                    }
                }
                dealingOutcome.CatersCosts = (object[][])db.select("cater, type, cost").from("Cater, Cater_provider").
                    where("cater_service_id=Cater.id").and("cater_provider_id='" + caterBestOffer.ServiceProviderID + "'").
                    and(GatewayAgent.myConf).execute();

                db.update("Hotel_offer").set("accepted", "1").where("hotel_id='" + residenceBestOffer.ServiceProviderID + "'").execute();
                db.update("Cater_provider_offer").set("accepted", "1").where("cater_provider_id='" + caterBestOffer.ServiceProviderID + "'").execute();
                string hotelEmail = ((string)db.selectCell("email").from("Hotel").where("id='" + residenceBestOffer.ServiceProviderID + "'").execute());
                string acceptanceMessage = "We are glad to inform you that we have accepted your serving offer, thanks for your co-operation.";
                string caterProviderEmail = ((string)db.selectCell("email").from("Cater_provider").where("id='" + caterBestOffer.ServiceProviderID + "'").execute());
                ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { hotelEmail, caterProviderEmail }, acceptanceMessage);
                step = FINALIZE_DEALS;
                // sendOfferAcceptance
                // generateReport
            }
        }

        private void decreaseCostByDistributingService() {
            List<HotelRoom> bestRooms = new List<HotelRoom>(3);
            bestRooms[0] = getBestRoom(1);
            bestRooms[1] = getBestRoom(2);
            bestRooms[2] = getBestRoom(3);

            int newResidenceTotalCost = 0;
            foreach (HotelRoom room in bestRooms) {
                if (room != null) {
                    newResidenceTotalCost += room.TotalCost;
                }
            }
            ServiceProviderTotalCost previousResidenceBestOffer = hotelsTotalCosts[0];
            if (previousResidenceBestOffer.TotalCost - newResidenceTotalCost >= overBudget) {
                // horrrrray

                dealingOutcome.Outcome = DealingOutcome.OFFERS_ACCEPTED;
                dealingOutcome.OfferAcceptanceMethod = DealingOutcome.AFTER_DISTRIBUTING_SERVICE;
                dealingOutcome.ResidenceTotalCost = newResidenceTotalCost;
                dealingOutcome.CaterTotalCost = caterersTotalCosts[0].TotalCost;
                dealingOutcome.TotalCost = dealingOutcome.ResidenceTotalCost + dealingOutcome.CaterTotalCost;

                dealingOutcome.CatersCosts = (object[][])db.select("cater, type, cost").from("Cater, Cater_provider").
                    where("cater_service_id=Cater.id").and("cater_provider_id='" + caterersTotalCosts[0].ServiceProviderID + "'").
                    and(GatewayAgent.myConf).execute();
                dealingOutcome.RoomDistribution = new string[3][];

                foreach (HotelRoom room in bestRooms) {
                    if (room != null) {
                        db.update("Hotel_offer").set("accepted", "1").where("hotel_id='" + room.HotelID + "'").and(GatewayAgent.myConf).execute();
                        string[] hotelDetails = ((string[][])db.select("name, email").from("Hotel").where("id='" + room.HotelID + "'").execute())[0];

                        List<int> types = new List<int>();
                        types.Add(room.Type);
                        foreach (HotelRoom r in bestRooms) {
                            if (r != null) {
                                dealingOutcome.RoomsCosts[r.Type - 1] = room.OfferedCost;
                                if (r.HotelID == room.HotelID && r.Type != room.Type) {
                                    types.Add(r.Type);
                                    bestRooms.Remove(r);
                                }
                            }
                        }
                        bestRooms.Remove(room);
                        string acceptanceMessage = "We are glad to inform you that we have accepted your serving offer for the following room(s) types\n";
                        foreach (int type in types) {
                            dealingOutcome.RoomDistribution[type - 1] = hotelDetails;
                            switch (type) {
                                case 1:
                                    acceptanceMessage += "- Suit\n";
                                    break;
                                case 2:
                                    acceptanceMessage += "- Deluxe\n";
                                    break;
                                case 3:
                                    acceptanceMessage += "- Standard\n";
                                    break;
                            }
                        }
                        acceptanceMessage += "Thanks for your co-operation.";
                        ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { hotelDetails[1] }, acceptanceMessage);
                    }
                }
            } else {
                decreaseCostByDegradingService(newResidenceTotalCost, bestRooms);
            }
        }

        private void decreaseCostByDegradingService(int newResidenceTotalCost, List<HotelRoom> bestRooms) {
            /*bool hasDeluxeRoom = false;
            bool hasStandardRoom = false;
            foreach (HotelRoom room in offeredRooms) {
                if (room.Type == 2) {
                    hasDeluxeRoom = true;
                }
                if (room.Type == 3) {
                    hasStandardRoom = true;
                }
            }*/
            bool hasDeluxeRoom = (bestRooms[1] == null) ? false : true;
            bool hasStandardRoom = (bestRooms[2] == null) ? false : true;
            if (!hasDeluxeRoom) {
                // generate report can't solve over budget
                // issue: there is no Deluxe rooms to degrade
                dealingOutcome.Outcome = DealingOutcome.OVER_BUDGET;
                Message msg = new Message();
                msg.Purpose = Communicator.DEALING_ISSUE;
                List<object> content = new List<object>();
                content.Add(1); // significance
                content.Add("Can't solve over budget issue.\n" +
                "Reason: There is no Deluxe rooms to degrade them to standard rooms," +
                "which is insufficient for the system to perform service degrading.");
                msg.Data = JsonConvert.SerializeObject(content);
                ACLMessage aclMessage = new ACLMessage(ACLMessage.INFORM);
                aclMessage.addReceiver(new AID("Reporter", AID.ISLOCALNAME));
                aclMessage.setContent(JsonConvert.SerializeObject(msg));
                myAgent.send(aclMessage);
                return;
            }
            if (!hasStandardRoom) {
                // generate report can't solve over budget
                // issue: there is no info about standard rooms to degrade deluxe rooms to
                Message msg = new Message();
                msg.Purpose = Communicator.DEALING_ISSUE;
                List<object> content = new List<object>();
                content.Add(1); // significance
                content.Add("Can't solve over budget issue.\n"+
                "Reason: There is no details about standard rooms to degrade deluxe rooms to,"+
                "which is insufficient for the system to perform service degrading.");
                msg.Data = JsonConvert.SerializeObject(content);
                ACLMessage aclMessage = new ACLMessage(ACLMessage.INFORM);
                aclMessage.addReceiver(new AID("Reporter", AID.ISLOCALNAME));
                aclMessage.setContent(JsonConvert.SerializeObject(msg));
                myAgent.send(aclMessage);
                dealingOutcome.Outcome = DealingOutcome.OVER_BUDGET;
                return;
            }
            /*int degradeDifference = 0;
            int bestHotelID = 0;
            List<HotelRoom> availableRooms = new List<HotelRoom>(offeredRooms);
            foreach (HotelRoom deluxeRoom in availableRooms) {
                if (deluxeRoom.Type == 2) {
                    foreach (HotelRoom standardRoom in availableRooms) {
                        if (standardRoom.Type == 3) {
                            if (standardRoom.HotelID == deluxeRoom.HotelID) {
                                if (deluxeRoom.OfferedCost - standardRoom.OfferedCost > degradeDifference) {
                                    degradeDifference = deluxeRoom.OfferedCost - standardRoom.OfferedCost;
                                    bestHotelID = deluxeRoom.HotelID;
                                    availableRooms.Remove(deluxeRoom);
                                    availableRooms.Remove(standardRoom);
                                }
                            }
                        }
                    }
                }
            }*/
            //List<HotelRoom> availableRooms = new List<HotelRoom>(offeredRooms);
            //HotelRoom lowestStandardRoom = getBestRoom(3);
            /*new HotelRoom();
            lowestStandardRoom.OfferedCost = 999999999;
            foreach (HotelRoom standardRoom in availableRooms) {
                if (standardRoom.Type == 3) {
                    if (standardRoom.OfferedCost < lowestStandardRoom.OfferedCost) {
                        lowestStandardRoom = standardRoom;
                    }
                }
            }
            int degradeDifference = 0;
            int previousBestOfferHotelID = hotelsTotalCosts[0].ServiceProviderID;
            foreach (HotelRoom deluxeRoom in availableRooms) {
                if (deluxeRoom.Type == 2 && deluxeRoom.HotelID == previousBestOfferHotelID) {
                    degradeDifference = deluxeRoom.OfferedCost - lowestStandardRoom.OfferedCost;
                    break;
                }
            }
            HotelRoom previousStandardRoom;
            foreach (HotelRoom standardRoom in availableRooms) {
                if (standardRoom.Type == 3 && standardRoom.HotelID == previousBestOfferHotelID) {
                    previousStandardRoom = standardRoom;
                    break;
                }
            }*/

            /*int standardRoomServiceID = ((int[][])db.selectCell("id").from("Residence_service").
                where("room_type='3'").and(GatewayAgent.myConf).execute())[0][0];
            int nStandardRoom = ((int[][])db.select("count(residence_service_id)").from("Attendee").
                where("residence_service_id='" + standardRoomServiceID + "'").and(GatewayAgent.myConf).execute())[0][0];
            int[] attendeesIDs = (int[])db.selectColumn("id").from("Attendee").
                where("residence_service_id='" + standardRoomServiceID + "'").and(GatewayAgent.myConf).execute();
            int totalDays = 0;
            foreach (int attendeeID in attendeesIDs) {
                DateTime[][] arrival_departue_dates = (DateTime[][])db.select("arrival_date, departure_date").
                    from("Attendee_arrival_details").where("attendee_id='" + attendeeID + "'").and(GatewayAgent.myConf).execute();
                totalDays += arrival_departue_dates[0][1].Subtract(arrival_departue_dates[0][0]).Days;
            }*/

            ServiceProviderTotalCost previousResidenceBestOffer = hotelsTotalCosts[0];
            int degradeDifference = bestRooms[1].OfferedCost - bestRooms[2].OfferedCost;

            int nDegradedRoom = 1;
            while (previousResidenceBestOffer.TotalCost - (newResidenceTotalCost - (degradeDifference * nDegradedRoom)) >= overBudget) {
                nDegradedRoom++;
            }
            /*while (degradeDifference * nDegradedRoom < overBudget) {
                nDegradedRoom++;
            }*/
            int deluxeRoomServiceID = ((int[][])db.selectCell("id").from("Residence_service").where("room_type='2'").and(GatewayAgent.myConf).execute())[0][0];
            int nDeluxeRoom = ((int[][])db.select("count(residence_service_id)").from("Attendee").
                            where("residence_service_id='" + deluxeRoomServiceID + "'").and(GatewayAgent.myConf).execute())[0][0];
            int standardRoomServiceID = ((int[][])db.selectCell("id").from("Residence_service").
                where("room_type='3'").and(GatewayAgent.myConf).execute())[0][0];
            int nStandardRoom = ((int[][])db.select("count(residence_service_id)").from("Attendee").
                where("residence_service_id='" + standardRoomServiceID + "'").and(GatewayAgent.myConf).execute())[0][0];
            if (nDegradedRoom > nDeluxeRoom) {
                // generate report can't solve over budget
                // issue: degraded all deluxe rooms and there is still over budget
                Message msg = new Message();
                msg.Purpose = Communicator.DEALING_ISSUE;
                List<object> content = new List<object>();
                content.Add(1); // significance
                content.Add("Can't solve over budget issue.\n" +
                "Reason: Degraded all deluxe rooms and there is still over budget.");
                msg.Data = JsonConvert.SerializeObject(content);
                ACLMessage aclMessage = new ACLMessage(ACLMessage.INFORM);
                aclMessage.addReceiver(new AID("Reporter", AID.ISLOCALNAME));
                aclMessage.setContent(JsonConvert.SerializeObject(msg));
                myAgent.send(aclMessage);
            } else {
                // horrrraaay
                dealingOutcome.RoomDistribution = new string[3][];
                dealingOutcome.DegradedRooms = nDegradedRoom;
                dealingOutcome.DeluxeRooms = nDeluxeRoom - nDegradedRoom;
                dealingOutcome.StandardRooms = nStandardRoom;

                bool allowServiceDegrading = (bool)db.selectCell("allow_degrading").from("Deals_details").where(GatewayAgent.myConf).execute();
                if (allowServiceDegrading) {
                    // generate report you need to choose nDegraded person(s) to degrade their rooms to standard room in order to overcome the budget shortage
                    // send emails
                    if (nDeluxeRoom - nDegradedRoom > 0) {
                        string[] deluxeHotelDetails = ((string[][])db.select("name, email").from("Hotel").
                            where("id='" + bestRooms[1].HotelID + "'").and(GatewayAgent.myConf).execute())[0];
                        string deluxeAcceptanceMessage = "We are glad to inform you that we have accepted your serving offer," +
                        "and we would like to rent " + (nDeluxeRoom - nDegradedRoom) + " Deluxe room(s) in your hotel, thanks for your co-operation.";
                        ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { deluxeHotelDetails[1] }, deluxeAcceptanceMessage);
                        dealingOutcome.RoomDistribution[1] = deluxeHotelDetails;
                    }

                    string[] standardHotelDetails = ((string[][])db.selectCell("email").from("Hotel").
                        where("id='" + bestRooms[2].HotelID + "'").and(GatewayAgent.myConf).execute())[0];
                    string standardAcceptanceMessage = "We are glad to inform you that we have accepted your serving offer," +
                    "and we would like to rent " + (nDegradedRoom + nStandardRoom) + " Standard room(s) in your hotel, thanks for your co-operation.";
                    ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { standardHotelDetails[1] }, standardAcceptanceMessage);
                    dealingOutcome.RoomDistribution[2] = standardHotelDetails;

                    if (bestRooms[0] != null) {
                        string suitAcceptanceMessage = "We are glad to inform you that we have accepted your serving offer for the suit room type, Thanks for your co-operation.";
                        string[] suitHotelDetails = ((string[][])db.selectCell("email").from("Hotel").
                        where("id='" + bestRooms[0].HotelID + "'").and(GatewayAgent.myConf).execute())[0];
                        ((Communicator)myAgent).sendOfferAcceptanceEmail(new string[] { suitHotelDetails[1] }, suitAcceptanceMessage);
                        dealingOutcome.RoomDistribution[0] = suitHotelDetails;
                    }

                    dealingOutcome.Outcome = DealingOutcome.OFFERS_ACCEPTED;
                    dealingOutcome.OfferAcceptanceMethod = DealingOutcome.ALLOW_SERVICE_DEGRADE;
                    dealingOutcome.ResidenceTotalCost = newResidenceTotalCost;
                    dealingOutcome.CaterTotalCost = caterersTotalCosts[0].TotalCost;
                    dealingOutcome.TotalCost = dealingOutcome.ResidenceTotalCost + dealingOutcome.CaterTotalCost;
                    foreach (HotelRoom room in bestRooms) {
                        if (room != null) {
                            if (room.Type == 2 && nDeluxeRoom - nDegradedRoom <= 0) {
                                continue;
                            }
                            dealingOutcome.RoomsCosts[room.Type - 1] = room.OfferedCost;
                        }
                    }

                    dealingOutcome.CatersCosts = (object[][])db.select("cater, type, cost").from("Cater, Cater_provider").
                        where("cater_service_id=Cater.id").and("cater_provider_id='" + caterersTotalCosts[0].ServiceProviderID + "'").
                        and(GatewayAgent.myConf).execute();
                } else {
                    dealingOutcome.Outcome = DealingOutcome.ALLOW_SERVICE_DEGRADE;
                    // generate report there is an over budget but we can overcome it by degrading nDeluxeRoom to standard rooms
                }
            }
        }

        public void allowServiceDegrade() {
            List<HotelRoom> bestRooms = new List<HotelRoom>(3);
            bestRooms[0] = getBestRoom(1);
            bestRooms[1] = getBestRoom(2);
            bestRooms[2] = getBestRoom(3);
            int newResidenceTotalCost = 0;
            foreach (HotelRoom room in bestRooms) {
                if (room != null) {
                    newResidenceTotalCost += room.TotalCost;
                }
            }
            dealingOutcome.Outcome = DealingOutcome.OFFERS_ACCEPTED;
            dealingOutcome.OfferAcceptanceMethod = DealingOutcome.AFTER_DECREASING_SERVICE;
            dealingOutcome.ResidenceTotalCost = newResidenceTotalCost;
            dealingOutcome.CaterTotalCost = caterersTotalCosts[0].TotalCost;
            dealingOutcome.TotalCost = dealingOutcome.ResidenceTotalCost + dealingOutcome.CaterTotalCost;
            foreach (HotelRoom room in bestRooms) {
                if (room != null) {
                    if (room.Type == 2 && dealingOutcome.DeluxeRooms - dealingOutcome.DegradedRooms <= 0) {
                        continue;
                    }
                    dealingOutcome.RoomsCosts[room.Type - 1] = room.OfferedCost;
                }
            }
            dealingOutcome.CatersCosts = (object[][])db.select("cater, type, cost").from("Cater, Cater_provider").
                where("cater_service_id=Cater.id").and("cater_provider_id='" + caterersTotalCosts[0].ServiceProviderID + "'").
                and(GatewayAgent.myConf).execute();

            string[] suitHotelDetails = ((string[][])db.selectCell("email").from("Hotel").
                        where("id='" + bestRooms[0].HotelID + "'").and(GatewayAgent.myConf).execute())[0];
            dealingOutcome.RoomDistribution[0] = suitHotelDetails;

            string[] deluxeHotelDetails = ((string[][])db.select("name, email").from("Hotel").
                            where("id='" + bestRooms[1].HotelID + "'").and(GatewayAgent.myConf).execute())[0];
            dealingOutcome.RoomDistribution[1] = deluxeHotelDetails;

            string[] standardHotelDetails = ((string[][])db.selectCell("email").from("Hotel").
                        where("id='" + bestRooms[2].HotelID + "'").and(GatewayAgent.myConf).execute())[0];
            dealingOutcome.RoomDistribution[2] = standardHotelDetails;
        }

        private HotelRoom getBestRoom(int roomType) {
            int previousBestOfferHotelID = hotelsTotalCosts[0].ServiceProviderID;
            HotelRoom bestRoom = null;
            foreach (HotelRoom room in offeredRooms) {
                if (room.HotelID == previousBestOfferHotelID && room.Type == roomType) {
                    bestRoom = room;
                }
            }
            if (bestRoom == null) {
                // didn't request renting this room type
                return null;
            }
            foreach (HotelRoom room in offeredRooms) {
                if (room.HotelID != previousBestOfferHotelID) {
                    if (room.TotalCost < bestRoom.TotalCost) {
                        bestRoom = room;
                        //bestRoomCost = room.TotalCost;
                        //bestRoomHotel = room.HotelID;
                    }
                }
            }
            return new HotelRoom(bestRoom);
        }
    }

    public class ServiceProviderTotalCost : IComparable<ServiceProviderTotalCost> {
        public ServiceProviderTotalCost(int serviceProviderID, int totalCost) {
            ServiceProviderID = serviceProviderID;
            TotalCost = totalCost;
        }
        public int ServiceProviderID {
            get;
            set;
        }
        public int TotalCost {
            get;
            set;
        }
        public int CompareTo(ServiceProviderTotalCost other) {
            return this.TotalCost.CompareTo(other.TotalCost);
        }
    }

    public class HotelRoom {
        public HotelRoom() {
        }
        public HotelRoom(HotelRoom room) {
            Type = room.Type;
            OfferedCost = room.OfferedCost;
            TotalCost = room.TotalCost;
            HotelID = room.HotelID;
        }
        public int Type {
            get;
            set;
        }
        public int OfferedCost {
            get;
            set;
        }
        public int TotalCost {
            get;
            set;
        }
        public int HotelID {
            get;
            set;
        }
    }
}
